/*
 * EventListenerList.java  0.4.0 / Oct 23, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.core.events;

import java.lang.reflect.Array;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;

/**
 * EventListener List.
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class EventListenerList {

// Private Static Fields. ------------------------------------------------------

  private static final int INITIAL_CAPACITY = 3;

// Private Fields. -------------------------------------------------------------

  private final HashMap listenerGroups;
  private final HashMap listenerGroupCounts;
  private int listenerCount;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new EventListenerList object.
   */
  public EventListenerList() {
    listenerGroups = new HashMap(INITIAL_CAPACITY);
    listenerGroupCounts = new HashMap(INITIAL_CAPACITY);
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Adds new listener.
   *
   * @param listenerType the type of the listener
   * @param listener the listener
   */
  public void add(Class listenerType, EventListener listener) {
    if (listener != null) {
      int size = 0;
      EventListener[] listeners;
      Integer count = (Integer)listenerGroupCounts.get(listenerType);
      if (count == null) {
        listeners = (EventListener[])Array.newInstance(listenerType,
                                                       INITIAL_CAPACITY);
        listenerGroups.put(listenerType, listeners);
      } else {
        size = count.intValue();
        listeners = (EventListener[])listenerGroups.get(listenerType);
        if (size == listeners.length) {
          EventListener[] oldListeners = listeners;
          int length = (3 * size) >> 1;
          listeners = (EventListener[])Array.newInstance(listenerType,
                                                         ++length);
          System.arraycopy(oldListeners, 0, listeners, 0, size);
          listenerGroups.put(listenerType, listeners);
        }
      }
      listeners[size] = listener;
      listenerGroupCounts.put(listenerType, new Integer(++size));
      listenerCount++;
    }
  }

  /**
   * Removes specified listener.
   *
   * @param listenerType the type of the listener
   * @param listener the listener to be removed
   */
  public void remove(Class listenerType, EventListener listener) {
    if (listener != null) {
      Integer count = (Integer)listenerGroupCounts.get(listenerType);
      if (count != null) {
        int size = count.intValue();
        EventListener[] listeners = (EventListener[])listenerGroups.
                                                             get(listenerType);
        int index = -1;
        for (int i = size; --i >= 0;) {
          if (listeners[i] == listener) {
            index = i;
            break;
          }
        }
        if (index < 0) {
          return;
        }
        listenerCount--;
        if ((size == 1) && (index == 0)) {
          listeners[0] = null;
          listenerGroupCounts.remove(listenerType);
          listenerGroups.remove(listenerType);
          return;
        }
        EventListener[] oldListeners = listeners;
        int halfLength = listeners.length >> 1;
        boolean split = size < halfLength;
        if (split) {
          listeners = (EventListener[])Array.newInstance(listenerType,
                                                         halfLength);
          listenerGroups.put(listenerType, listeners);
        }
        int numMoved = size - index - 1;
        if (numMoved > 0) {
          System.arraycopy(oldListeners, index + 1, listeners, index, numMoved);
        }
        size--;
        if (!split) {
          listeners[size] = null;
        }
        listenerGroupCounts.put(listenerType, new Integer(size));
      }
    }
  }

  /**
   * Removes all listeners of the specified type.
   *
   * @param listenerType the listener type
   */
  public void removeAll(Class listenerType) {
    Integer count = (Integer)listenerGroupCounts.get(listenerType);
    if (count != null) {
      listenerGroupCounts.put(listenerType, null);
      listenerGroups.put(listenerType, null);
      listenerCount -= count.intValue();
    }
  }

  /**
   * Removes all listeners.
   */
  public void removeAll() {
    listenerGroupCounts.clear();
    listenerGroups.clear();
    listenerCount = 0;
  }

  /**
   * Returns the number of listener groups.
   *
   * @return the number of listener groups
   */
  public int getListenerCount() {
    return listenerCount;
  }

  /**
   * Returns the number of listeners specified by the listener type.
   *
   * @param listenerType the type of the listener
   *
   * @return the number of listeners specified by the listener type
   */
  public int getListenerCount(Class listenerType) {
    Integer count = (Integer)listenerGroupCounts.get(listenerType);
    if (count != null) {
      return count.intValue();
    }
    return 0;
  }

  /**
   * Returns the number of listener groups.
   *
   * @return the number of listener groups
   */
  public int getListenerGroupsCount() {
    return listenerGroupCounts.size();
  }

  /**
   * Returns <tt>true</tt> if the specified listener is added.
   *
   * @param listenerType the type of the listener
   * @param listener the listener
   *
   * @return <tt>true</tt> if the specified listener is added
   */
  public boolean containsListener(Class listenerType, EventListener listener) {
    if ((listenerType != null) && (listener != null)) {
      int count = getListenerCount(listenerType);
      if (count > 0) {
        EventListener[] listeners =
                             (EventListener[])listenerGroups.get(listenerType);
        for (int i = count; --i >= 0;) {
          if (listeners[i] == listener) { // NOPMD
            return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * Returns listeners specified by the listener type.
   *
   * @param listenerType the type of the listener
   *
   * @return listeners specified by the listener type
   */
  public EventListener[] getListeners(Class listenerType) {
    int count = getListenerCount(listenerType);
    EventListener[] listeners = new EventListener[count];
    if (count > 0) {
      System.arraycopy(listenerGroups.get(listenerType), 0, listeners, 0,
                       count);
    }
    return listeners;
  }

  /**
   * Returns listeners specified by the listener type.
   * <p>
   *   <tt>WARNING: This method does not return copy of listeners!</tt>
   * </p>
   *
   * @param listenerType the type of the listener
   *
   * @return listeners specified by the listener type
   */
  public EventListener[] getListenersArray(Class listenerType) {
    return (EventListener[])listenerGroups.get(listenerType); // NOFB
  }

// Overridden Methods. ---------------------------------------------------------
  /**
   * {@inheritDoc}
   */
  public String toString() {
    StringBuffer sb = new StringBuffer(80);
    sb.append("EventListenerList: ");
    sb.append(listenerCount);
    sb.append(" listeners: ");
    HashMap listenerGroups = this.listenerGroups;
    HashMap listenerGroupCounts = this.listenerGroupCounts;
    Iterator it = listenerGroups.keySet().iterator(); // NOFB
    while (it.hasNext()) {
      Class type = (Class)it.next();
      EventListener[] listeners = // NOFB
          (EventListener[])listenerGroups.get(type); // NOFB
      Integer count = (Integer)listenerGroupCounts.get(type);
      for (int i = count.intValue(); --i >= 0;) {
        sb.append(" type ");
        sb.append(type.getName());
        sb.append(" listener ");
        sb.append(listeners[i]);
      }
    }
    return sb.toString();
  }

}
